package com.cib.ajax.rpc;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jabsorb.JSONRPCResult;
import org.jabsorb.JSONRPCServlet;
import org.json.JSONArray;
import org.json.JSONObject;

import com.cib.ajax.rpc.util.JSON2JavaBean;
import com.cib.ajax.rpc.util.JSON2JavaMethodUtil;
import com.cib.ajax.rpc.util.JavaBean2JSON;

public class CommonJSONRPCServiceServlet extends JSONRPCServlet {

	private static final long serialVersionUID = 1L;
	transient Class<?> serviceInterface = null;
	transient Object serviceInstance = null;
	private JSON2JavaMethodUtil methodUtil = new JSON2JavaMethodUtil();

	/**
	 * @see JSONRPCServlet#JSONRPCServlet()
	 */
	public CommonJSONRPCServiceServlet() {
		super();
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		try {
			String interfaceClazz = config.getInitParameter("interface");
			this.serviceInterface = Class.forName(interfaceClazz);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		try {
			String implementationClazz = config
					.getInitParameter("implementation");
			this.serviceInstance = Class.forName(implementationClazz)
					.newInstance();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public void service(HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		// Either a GET or a POST that's not an XHR call, such as after login
		// via j_security_check that tries to forward to last requested URL.

		if (request.getMethod().equals("GET")) {
			doGet(request, response);
		} else {

			try {
				handleServiceRequest(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				HttpSession session = request.getSession(false);
				if (session != null) {
					session.removeAttribute("JSONRPCBridge");
				}
			}
		}
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws IOException {

		try {
			handleServiceRequest(request, response);
		} finally {
			HttpSession session = request.getSession(false);
			if (session != null) {
				session.removeAttribute("JSONRPCBridge");
			}
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		doGet(request, response);
	}

	private void handleServiceRequest(HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		response.setContentType("text/plain;charset=utf-8");
		OutputStream out = response.getOutputStream();

		String charset = request.getCharacterEncoding();
		if (charset == null) {
			charset = "UTF-8";
		}
		BufferedReader in = new BufferedReader(new InputStreamReader(request
				.getInputStream(), charset));

		// Read the request
		CharArrayWriter data = new CharArrayWriter();
		char[] buf = new char[4096];
		int ret;
		while ((ret = in.read(buf, 0, 4096)) != -1) {
			data.write(buf, 0, ret);
		}

		JSONObject jsonReq = null;
		//String method = null;
		try {
			System.out.println(data.toString());
			String reqData = URLDecoder.decode(data.toString(), "UTF-8");
			jsonReq = new JSONObject(reqData);
			//method = jsonReq.getString("method");
		} catch (Exception e) {
			e.printStackTrace();
		}

		byte[] bout = null;
		bout = handleJSONRPCMethodInvocation(request, response, jsonReq);

		// Send response to client
		out.write(bout);
		out.flush();
		out.close();
	}
	
	protected byte[] handleJSONRPCMethodInvocation(HttpServletRequest request,
			HttpServletResponse response, JSONObject jsonReq)
			throws IOException, UnsupportedEncodingException {

		String method = null;
		Object[] args = null;
		Object id = null;
		Method jsonOperation = null;
		try {
				
			// Extract the method
			method = jsonReq.getString("method");
			jsonOperation = methodUtil.findOperation(method,this.serviceInterface);
			
			// Extract the arguments			
			args = methodUtil.transferToJavaParameters(jsonReq, this.serviceInterface);
			
		} catch (Exception e) {
			JSONRPCResult errorResult = new JSONRPCResult(
					JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
			return errorResult.toString().getBytes("UTF-8");
		}

		// invoke the request		
		Object result = null;
		Object[] parameters = null;
		if (null != args && hasSessionParam(jsonOperation)) {
			parameters = new Object[args.length + 1];
			parameters[0] = request.getSession(false);
			for (int i = 0; i < args.length; i++) {
				parameters[i + 1] = args[i];
			}
		} else {
			parameters = args;
		}

		try {
			JSONObject jsonResponse = new JSONObject();
					
			// result = wire.invoke(jsonOperation, args);
			result = jsonOperation.invoke(this.serviceInstance, parameters);
			JavaBean2JSON transform = new JavaBean2JSON();

			try {
				// FIXME: this only checks for String and not primitive types
				if (!(result instanceof java.lang.String)) {
					jsonResponse.put("result", transform.toJSON(result));
				} else {
					jsonResponse.put("result", result);
				}
				jsonResponse.putOpt("id", id);

				// get response to send to client
				return jsonResponse.toString().getBytes("UTF-8");
			} catch (Exception e) {
				JSONRPCResult errorResult = new JSONRPCResult(
						JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
				return errorResult.toString().getBytes("UTF-8");
			}
		} catch (InvocationTargetException e) {
			JSONRPCResult errorResult = new JSONRPCResult(
					JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
			return errorResult.toString().getBytes("UTF-8");
		} catch (RuntimeException e) {
			JSONRPCResult errorResult = new JSONRPCResult(
					JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
			return errorResult.toString().getBytes("UTF-8");
		} catch (IllegalAccessException e) {
			JSONRPCResult errorResult = new JSONRPCResult(
					JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
			return errorResult.toString().getBytes("UTF-8");
		} catch (Exception e) {
			JSONRPCResult errorResult = new JSONRPCResult(
					JSONRPCResult.CODE_REMOTE_EXCEPTION, id, e.getCause());
			return errorResult.toString().getBytes("UTF-8");
		}
	}	

	/**
	 * 只有第一个参数可以是session类型的 当第一个参数是session类型的，则将session也作为参数传入Service方法
	 * 
	 * @param method
	 * @return
	 */
	private boolean hasSessionParam(Method method) {
		// Note that only the first parameter can be HTTPSession
		Class<?>[] params = method.getParameterTypes();
		if (null != params
				&& params.length > 0
				&& params[0].getCanonicalName().compareToIgnoreCase(
						"javax.servlet.http.HttpSession") == 0) {
			return true;
		}
		return false;
	}

}
